#pragma once

#include <jni.h>
#include "jtype.h"
#include <gtl/string/str.h>
#include <gtl/io/ostrm.h>

namespace gtl
{

namespace jni
{

class caller
{
public:
	caller()
	{
		m_env = NULL;
		m_class = NULL;
		m_object = NULL;
	}

	caller(JNIEnv* env, jobject object)
	{
		set_object_info(env, object)
	}

public:
	void set_object_info(JNIEnv* env, jobject object)
	{
		m_env = env;
		m_object = object;
		m_class = NULL;

		if(m_env == NULL || m_object == NULL)
			return;

		m_class = m_env->GetObjectClass(m_object);
		catch_exception();
	}

	void catch_exception()
	{
		if(m_env == NULL)
			return;

		jthrowable jexcp = m_env->ExceptionOccurred();
		if(jexcp == 0)
			return;

		jclass jcls = m_env->GetObjectClass(jexcp);  
		m_env->ExceptionClear();

		jmethodID mid = m_env->GetMethodID(jcls, "toString", "()Ljava/lang/String;");  
		jstring msg = (jstring) m_env->CallObjectMethod(jexcp, mid);  
		m_env->ExceptionDescribe();         
		m_env->ExceptionClear();  

		gtl::dout << m_env->GetStringUTFChars(msg, NULL) << gtl::eol;
	}

public:
	void call(const char* func_name)
	{
		if(func_name == NULL || m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void()>::def());
		m_env->CallVoidMethod(m_object, jmid);
		catch_exception();
	}

	template<class T>
	void call(const char* func_name, T val)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val);
		catch_exception();
	}

	template<class T1, class T2>
	void call(const char* func_name, T1 val1, T2 val2)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T1, T2)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val1, val2);
		catch_exception();
	}

	template<class T1, class T2, class T3>
	void call(const char* func_name, T1 val1, T2 val2, T3 val3)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T1, T2, T3)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val1, val2, val3);
		catch_exception();
	}

	template<class T1, class T2, class T3, class T4>
	void call(const char* func_name, T1 val1, T2 val2, T3 val3, T4 val4)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T1, T2, T3, T4)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val1, val2, val3, val4);
		catch_exception();
	}

	template<class T1, class T2, class T3, class T4, class T5>
	void call(const char* func_name, T1 val1, T2 val2, T3 val3, T4 val4, T5 val5)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T1, T2, T3, T4, T5)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val1, val2, val3, val4, val5);
		catch_exception();
	}

	template<class T1, class T2, class T3, class T4, class T5, class T6>
	void call(const char* func_name, T1 val1, T2 val2, T3 val3, T4 val4, T5 val5, T6 val6)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T1, T2, T3, T4, T5, T6)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val1, val2, val3, val4, val5, val6);
		catch_exception();
	}

	template<class T1, class T2, class T3, class T4, class T5, class T6, class T7>
	void call(const char* func_name, T1 val1, T2 val2, T3 val3, T4 val4, T5 val5, T6 val6, T7 val7)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T1, T2, T3, T4, T5, T6, T7)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val1, val2, val3, val4, val5, val6, val7);
		catch_exception();
	}

	template<class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
	void call(const char* func_name, T1 val1, T2 val2, T3 val3, T4 val4, T5 val5, T6 val6, T7 val7, T8 val8)
	{
		if(m_env == NULL || m_class == NULL)
			return;

		jmethodID jmid = m_env->GetMethodID(m_class, func_name, func<void(T1, T2, T3, T4, T5, T6, T7, T8)>::def());
		catch_exception();

		m_env->CallVoidMethod(m_object, jmid, val1, val2, val3, val4, val5, val6, val7, val8);
		catch_exception();
	}

protected:
	template<class Func>
	struct func;

	template<class R>
	struct func<R()>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1>
	struct func<R(T1)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1, class T2>
	struct func<R(T1, T2)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				jtype<T2>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1, class T2, class T3>
	struct func<R(T1, T2, T3)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				jtype<T2>::type() <<
				jtype<T3>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1, class T2, class T3, class T4>
	struct func<R(T1, T2, T3, T4)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				jtype<T2>::type() <<
				jtype<T3>::type() <<
				jtype<T4>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1, class T2, class T3, class T4, class T5>
	struct func<R(T1, T2, T3, T4, T5)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				jtype<T2>::type() <<
				jtype<T3>::type() <<
				jtype<T4>::type() <<
				jtype<T5>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1, class T2, class T3, class T4, class T5, class T6>
	struct func<R(T1, T2, T3, T4, T5, T6)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				jtype<T2>::type() <<
				jtype<T3>::type() <<
				jtype<T4>::type() <<
				jtype<T5>::type() <<
				jtype<T6>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
	struct func<R(T1, T2, T3, T4, T5, T6, T7)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				jtype<T2>::type() <<
				jtype<T3>::type() <<
				jtype<T4>::type() <<
				jtype<T5>::type() <<
				jtype<T6>::type() <<
				jtype<T7>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

	template<class R, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
	struct func<R(T1, T2, T3, T4, T5, T6, T7, T8)>
	{
		static gtl::str def()
		{
			return gtl::str() <<
				"(" <<
				jtype<T1>::type() <<
				jtype<T2>::type() <<
				jtype<T3>::type() <<
				jtype<T4>::type() <<
				jtype<T5>::type() <<
				jtype<T6>::type() <<
				jtype<T7>::type() <<
				jtype<T8>::type() <<
				")" <<
				jtype<R>::type();
		}
	};

protected:
	JNIEnv* m_env;
	jclass	m_class;
	jobject m_object;
};

} // end of namespace jni

} // end of namespace gtl
